Building a Clojure Web application with Incanter, Compojure, and Leiningen

This post will demonstrate how to build a simple Clojure Web application, with Compojure, that will use Incanter to generate a sample from a normal distribution with parameters (size, mean, and standard deviation) provided by the user, and then return a PNG image of the histogram of the data.

This example is simple, but it demonstrates how to dynamically generate an Incanter chart and pass it directly to the Web client as a PNG image.

For more information on the Compojure Web framework, visit its Github repository, its Google group, its WikiBooks site, and this quick tutorial.

I will use the Leiningen build tool to download dependencies, compile the application, and package it up as jar file. For more information on installing and using Leiningen, see my previous post.

Let’s start off by creating a project directory, I’ll call it incanter-webapp, and give it a src subdirectory. Then create the following Leiningen project.clj file in the project directory.

(defproject incanter-webapp "0.1.0"
  :description "A simple Incanter web-app."
  :dependencies [[incanter "1.2.1-SNAPSHOT"]
                 [compojure "0.3.2"]]
  :main simple_web_app)

This project file includes two dependencies, Incanter and Compojure, and indicates that the -main function for the project is in the file simple_web_app.clj in the src subdirectory of the project.

Next create the file called simple_web_app.clj in the src subdirectory.

(ns simple_web_app
  (:gen-class)
  (:use [compojure]
        [compojure.http response]
        [incanter core stats charts])
  (:import (java.io ByteArrayOutputStream
                    ByteArrayInputStream)))

Note that the namespace for this file is simple_web_app with underscores like in the file name and in the :main line of the project.clj file.

In Compojure, you need to define routes, which in this case associate the function sample-form with “/”, and the function gen-samp-hist-png with “/sample-normal”.

(defroutes webservice
  (GET "/"
    sample-form)
  (GET "/sample-normal"
    (gen-samp-hist-png request
                       (params :size)
                       (params :mean)
                       (params :sd))))

The function gen-samp-hist-png takes four arguments, a request object, the sample size, the population mean, and the population standard deviation, and then updates the Compojure response object to include an Incanter histogram of a sample from a normal distribution with the given parameters.

Now define the gen-samp-hist-png function; start by converting the string-arguments, passed in from the params object, into numbers.

(defn gen-samp-hist-png
  [request size-str mean-str sd-str]
    (let [size (if (nil? size-str)
                 1000
                 (Integer/parseInt size-str))
          m (if (nil? mean-str)
              0
              (Double/parseDouble mean-str))
          s (if (nil? sd-str)
              1
              (Double/parseDouble sd-str))

Each argument has a default value, 1000 for size, 0 for mean, and 1 for the standard deviation (sd).

Next generate the sample from the normal distribution using Incanter’s sample-normal function with the given size, mean, and standard deviation, and then create a histogram of the resulting data.

          samp (sample-normal size
                              :mean m
                              :sd s)
          chart (histogram
                  samp
                  :title "Normal Sample"
                  :x-label (str "sample-size = " size
                                ", mean = " m
                                ", sd = " s))

I’ve used the x-label option of the histogram function to customize the x-axis label so that it includes the given sample size, mean, and standard deviation.

Next, we need to convert the histogram into a byte array, and feed that into a ByteArrayInputStream that can be passed to Compojure’s update-response function, along with a header that sets the Content-Type to “image/png”.

        out-stream (ByteArrayOutputStream.)
        in-stream (do
                    (save chart out-stream)
                    (ByteArrayInputStream.
                      (.toByteArray out-stream)))
        header {:status 200
                :headers {"Content-Type" "image/png"}}]
    (update-response request
                     header
                     in-stream)))

The above code is the key to sending a dynamically generated chart directly to the client. In addition to a filename, an OutputStream can be passed to Incanter’s save function; in this case, we use a ByteArrayOutputStream, which is then converted to a byte array that is used to initialize the ByteArrayInputStream that is passed to update-response.

We can create a Web form for submitting requests to /sample-normal. The following function is a helper function that creates a simple html page skeleton.

(defn html-doc
  [title & body]
  (html
    (doctype :html4)
    [:html
      [:head
        [:title title]]
      [:body
       [:div
	[:h2
	 [:a {:href "/"}
          "Generate a normal sample"]]]
        body]]))

Now define the sample-form function, which will generate a Web form using html-doc. This function was associated with “/” earlier using the defroutes macro.

(def sample-form
  (html-doc "sample-normal histogram"
    (form-to [:get "/sample-normal"]
      "sample size: " (text-field {:size 4} :size)
      "mean: " (text-field {:size 4} :mean)
      "sd: " (text-field {:size 4} :sd)
      (submit-button "view"))))

Now, we need to create a -main function that will be called when the incanter-webapp.jar is executed. This function will call Compojure’s run-server function, starting the built-in Jetty server on port 8080.

(defn -main [& args]
  (run-server {:port 8080}
    "/*" (servlet webservice)))

Now we can use Leiningen to download and install all the necessary dependencies, including Incanter and Compojure, and then build and package the app.

$ lein deps
$ lein compile
$ lein uberjar

Finally, to start the server, run

$ java -jar incanter-webapp-standalone.jar 

and visit http://localhost:8080 to submit a request, or go directly to the sample-normal app by constructing a URL like the following:

http://localhost:8080/sample-normal?size=500&mean=100&sd=10

Which returns a PNG image of a histogram like this:

The complete code for this example can be found here, and the Leiningen project file can be found here.

15 responses to “Building a Clojure Web application with Incanter, Compojure, and Leiningen

  1. Pingback: Destillat KW49-2009 | duetsch.info - GNU/Linux, Open Source, Softwareentwicklung, Selbstmanagement, Vim ...

  2. Nice example.

    It worked out of the box for me (but if anyone is cutting/pasting the code chunks from this web page, rather than the raw code file, remember to remove ‘amp;’ from ‘&’ (two occurrances), as well as the third right-bracket ‘)’ at end of the line ‘(Double/parseDouble sd-str)))’ before compiling).

    Now I just have to learn/understand the code. :-(

    Thanks for posting.

    • Hi John,
      Thanks for catching those bugs, I just fixed them.

      To avoid any other weirdness introduced by cutting/pasting html, it’s probably best to grab the complete code from the link at the end of the post, or from the latest Incanter distribution in the examples/blog/projects/simple_web_app/ directory.

  3. Hi,

    Good simple tutorial.
    One more thing that might be useful.
    How would you recommend developing at a REPL with this setup?
    As it stands and code changes require a rebuild and restart of the server.

    thanks,
    James

    • Hi James,

      Check out Lau Jensen’s screencast on that very subject: http://www.bestinclass.dk/index.php/2009/12/dynamic-interactive-webdevelopment/

      David

      • Hi David,

        yeah I’ve seen that video. It’s a good demo but it’s not going into the detail of how you would get slime to pick up the clojure version and all the other jars downloaded by lein.

        For command line usage I’ve put together this variation of the common bash script, might be useful for others…

        http://gist.github.com/267746

        If I figure out how to do the same for emacs+slime I’ll post it here.

        thanks,
        James

      • Hi James,

        To set up interactive development of this app if you’re using Leiningen and Swank, just run:
        $ lein deps
        $ lein compile
        $ lein swank

        (I actually had to change my Incanter dependency to [incanter “1.0-SNAPSHOT”], instead of [incanter “1.0-master-SNAPSHOT”] because lein couldn’t find the later version for some reason.)

        Then from emacs, connect to the Swank server with ‘M-x slime-connect’, then evaluate the example file and start with server from the REPL with:
        (simple-web-app/-main)

        Make sure the app is working correctly by visiting: http://localhost:8080

        Now you should be able to make changes to the running app.

        Good luck,
        David

      • Thanks David,

        I’ll give it a whirl.

        James.

      • Works perfect.
        Thanks again.

        James

      • oops.. i just noticed you already had another post up about this exact thing. i’m a little behind on reading the blog :)

  4. This does NOT work with newer versions of Compojure.

  5. Nice one.
    I was looking for a Compojure kick-start and this article gives me what I need to understand Compojure.

    Thank you,


    Bahman

  6. Hi,

    I know this post is almost one year old, but would someone help me to run this? I downloaded Incanter and tried to build the corresponding example. “lein compile” fails like this:
    “Compiling simple_web_app
    Exception in thread “main” java.io.FileNotFoundException: Could not locate clojure/contrib/seq_utils__init.class or clojure/contrib/seq_utils.clj on classpath: (control.clj:9)”

    Any ideas?

  7. I managed to update the code in this post for use with the present libraries.

    In project.clj, dependencies are as follow:

    :dependencies [[org.clojure/clojure "1.2.0"]
    [org.clojure/clojure-contrib "1.2.0"]
    [compojure "0.5.2"]
    [ring/ring-jetty-adapter "0.3.1"]
    [hiccup "0.3.0"]
    [incanter "1.2.3-SNAPSHOT"]]

    The function that generates the graphic should return the whole header:

    {:status 200
    :headers {"Content-Type" "image/png"}
    :body in-stream}))

    Finally, main is changed to:

    (defn -main [& args]
    (run-jetty example {:port 8080}))

    The HTML generation has changed a bit (now it is dome by hiccup). Here is a good example of the new library usage.

Leave a comment